﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.Drawing;
using System.Collections.Specialized;
using System.Timers;
using System.Diagnostics;

namespace gpuHough.DirectX
{
    class Panel : System.Windows.Forms.Panel
    {
        

        private Device device;

        private int framesCounter = 0;
        private double fps;
        private Stopwatch stopwatch = new Stopwatch();
        
        private Size oldSize;

        public event EventHandler Load;
        public new event EventHandler Paint;

        public Texture TargetTexture { get; set; }
        public Texture SourceTexture { get; set; }
        public Surface ScreenSurface { get; set; }

        public double MaxFPS { get; set; }
        public double FPS
        {
            get { return fps; }
        }

        private Options permanentOptions;
        public Options PermanentOptions
        {
            get {
                return permanentOptions;
            }
            set {
                if (device != null) value.Apply(device, permanentOptions ?? new Options());
                permanentOptions = value;
            }
        }
        
        public Panel()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
        }

        protected override void OnParentChanged(EventArgs e)
        {   
            base.OnParentChanged(e);
            initDevice();
            OnLoad(new DXEventArgs(device));
        }

        private void initDevice()
        {
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true; // Specify that it will be in a window
            presentParams.SwapEffect = SwapEffect.Discard; // After the current screen is drawn, it will be automatically // deleted from memory
            presentParams.EnableAutoDepthStencil = false;
            presentParams.PresentationInterval = PresentInterval.Immediate;

            device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParams); // Put everything into the device

            device.RenderState.Lighting = false;
            device.RenderState.MultiSampleAntiAlias = false;
            device.RenderState.Clipping = false;
            device.RenderState.StencilEnable = false;
            device.RenderState.ColorVertex = false;
            device.RenderState.BlendOperation = BlendOperation.Add;
            device.RenderState.SourceBlend = Blend.BlendFactor;
            device.RenderState.DestinationBlend = Blend.One;
            device.RenderState.CullMode = Cull.None;
            device.RenderState.BlendFactor = Color.FromArgb(255, 255, 255, 255);
            device.RenderState.AlphaBlendEnable = false;

            device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
            device.SamplerState[0].MaxAnisotropy = 16;
            device.SamplerState[0].AddressU = TextureAddress.Border;
            device.SamplerState[0].AddressV = TextureAddress.Border;
            device.SamplerState[0].AddressW = TextureAddress.Border;
            device.SamplerState[0].BorderColor = Color.Black;
            if (PermanentOptions != null) PermanentOptions.Apply(device, new Options());
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (DesignMode)
            {
                e.Graphics.Clear(this.BackColor);
                base.OnPaint(e);
            }
            else
            {   
                stopwatch.Start();
                if (Paint != null) Paint(this, new DXEventArgs(device));
                device.Present();
                stopwatch.Stop();
                this.Invalidate();
            }

            if (framesCounter++ >= 20)
            {
                framesCounter = 0;
                fps = 20.0 * Stopwatch.Frequency / stopwatch.ElapsedTicks;                
                stopwatch.Reset();
            }
        }

        protected virtual void OnLoad(DXEventArgs e)
        {
            ScreenSurface = device.GetRenderTarget(0);
            if (Load != null) Load(this, e);
        }

        protected override void OnResize(EventArgs eventargs)
        {
            var newSize = ClientSize;

            if (Math.Abs(oldSize.Width - newSize.Width) > Math.Abs(oldSize.Height - newSize.Height))
                newSize.Height = newSize.Width;
            else
                newSize.Width = newSize.Height;

            newSize.Height = (int)Math.Pow(2, Math.Round(Math.Log(newSize.Height + 1) / Math.Log(2), 0));
            newSize.Width = (int)Math.Pow(2, Math.Round(Math.Log(newSize.Width + 1) / Math.Log(2), 0));

            oldSize = newSize;
            base.ClientSize = newSize;
            base.OnClientSizeChanged(eventargs);
        }

        /// <summary>
        /// Sets orthogonal projection:
        /// X - to the right  
        /// Y - to the bottom   
        /// Z - into the deep 
        /// (0,0) = left top corner
        /// (1,1) = right bottom corner
        /// </summary>
        public void SetOrthoViewProjection()
        {
            device.Transform.Projection = Matrix.OrthoRH(1, 1, 1, 50);
            device.Transform.View = Matrix.LookAtRH(new Vector3(0.5f, 0.5f, -10), new Vector3(0.5f, 0.5f, 0), new Vector3(0, -1, 0));
        }

        private void Render(Size targetSize, Options options, params IShape[] shapes)
        {
            options.Apply(device, PermanentOptions);
            device.SetTexture(0, options.TextureEnable.GetValueOrDefault() ? SourceTexture : null);

            device.Clear(ClearFlags.Target, this.BackColor, 1.0f, 0);
            device.BeginScene();

            if (options.Effect != null)
            {
                options.Effect.SetValue("g_mWorldViewProjection", device.Transform.View * device.Transform.Projection);
                options.Effect.SetValue("Tex1Shift", new float[] { 0.5f / targetSize.Width, 0.5f / targetSize.Height });
                options.Effect.SetValue("Tex1", SourceTexture);
                
                int numpasses = options.Effect.Begin(0);
                for (int pass = 0; pass < numpasses; pass++)
                {
                    options.Effect.BeginPass(pass);

                    foreach (var shape in shapes)
                        shape.RenderShape(device);

                    options.Effect.EndPass();
                }
                options.Effect.End();
            }
            else
            {
                foreach (var shape in shapes)
                    shape.RenderShape(device);
            }

            device.EndScene();
            options.Reset(device, PermanentOptions);
        }

        public void SwapTextures()
        {
            Texture tmp = TargetTexture;
            TargetTexture = SourceTexture;
            SourceTexture = tmp;
        }

        public void RenderToTexture(Options options, params IShape[] shapes)
        {
            device.SetRenderTarget(0, TargetTexture.DXTexture.GetSurfaceLevel(0));

            Render(TargetTexture.Size, options, shapes);
        }

        public void RenderToScreen(Options options, params IShape[] shapes)
        {
            device.SetRenderTarget(0, ScreenSurface);
            Render(ClientSize, options, shapes);
        }

        public Texture MakeTextureFromFile(string srcFile, int width, int height, int mipLevels, Usage usage, Format format, Pool pool, Filter filter, Filter mipFilter, int colorKey)
        {
            return new Texture(TextureLoader.FromFile(device, srcFile, width, height, mipLevels, usage, format, pool, filter, mipFilter, colorKey),
                new Size(width, height));
        }

        public Texture MakeTexture(int width, int height, int numLevels, Usage usage, Format format, Pool pool)
        {
            return new Texture(new Microsoft.DirectX.Direct3D.Texture(device, width, height, numLevels, usage, format, pool),
                new Size(width, height));
        }

        public Effect MakeEffectFromFile(string sourceDataFile, Include includeFile, string skipConstants, ShaderFlags flags, EffectPool pool)
        {
            string errors;
            Effect result = Effect.FromFile(device, sourceDataFile, includeFile, skipConstants, flags, pool, out errors);
            if (String.IsNullOrEmpty(errors))
                return result;
            else
                throw new Exception(errors);
        }
    }

    public class DXEventArgs : EventArgs
    {
        //temporary disabled for external usage
        //protected Device device { get; private set; }
        public DXEventArgs(Device device)
        {
            //this.device = device;
        }
    }

    public delegate void EventHandler(object sender, DXEventArgs e);
}
